package net.oschina.arvin.sqlbuilder;

import com.sun.media.sound.ModelConnectionBlock;
import net.oschina.arvin.sqlbuilder.definition.ColumnDefinition;
import net.oschina.arvin.sqlbuilder.definition.ModelDefinition;
import net.oschina.arvin.sqlbuilder.utils.AssertUtil;
import net.oschina.arvin.sqlbuilder.utils.Util;

import java.util.List;

/**
 * Sql 帮助类，快速构建 CRUD SQL Builder
 *
 * @author Arvin
 * @time 2017/11/18 18:27
 */
public class SqlHelper {

    /** 默认主键 ID 明天 */
    public static final String DEFAULT_PRIMARY_KEY_FIELD_NAME = "id";


    /**
     * 构建根据主键查询 SelectBuilder 对象， 主键查找规则：
     * 1. 查找 具有 @PrimaryKey 的字段列表作为主键
     * 2. 第一步骤没有找到的话，就使用 id 作为主键ID
     *
     * @param modelType        模型对象
     * @param primaryKeyValues 主键值列表，如果有多个的话，顺序就是在Model类定义的字段顺序
     * @return 返回 SelectBuilder
     * @throws net.oschina.arvin.sqlbuilder.exceptions.SqlBuilderException 如果主键值为null或者没有找到主键或者传的主键值和查找的主键类型不一致
     */
    public static GeneralSelectBuilder selectByPrimaryKey(Class<?> modelType, Object... primaryKeyValues) {
        return selectByPrimaryKey(modelType, "", primaryKeyValues);
    }

    /**
     * 构建根据主键查询 SelectBuilder 对象， 主键查找规则：
     * 1. 查找 具有 @PrimaryKey 的字段列表作为主键
     * 2. 第一步骤没有找到的话，就使用 id 作为主键ID
     *
     * @param tableName        数据库表名称
     * @param modelType        模型对象
     * @param primaryKeyValues 主键值列表，如果有多个的话，顺序就是在Model类定义的字段顺序
     * @return 返回 SelectBuilder
     * @throws net.oschina.arvin.sqlbuilder.exceptions.SqlBuilderException 如果主键值为null或者没有找到主键或者传的主键值和查找的主键类型不一致
     */
    public static GeneralSelectBuilder selectByPrimaryKey(String tableName, Class<?> modelType, Object... primaryKeyValues) {

        AssertUtil.assertNotNull(modelType, "模型类型不能为null");
        AssertUtil.assertTrue(primaryKeyValues != null && primaryKeyValues.length > 0, "必须提供主键值列表作为查询条件");

        int index = 1;
        for (Object primaryKeyValue : primaryKeyValues) {
            AssertUtil.assertNotNull(primaryKeyValue, "第[" + index++ + "]主键值不能为null");
        }

        // 获取主键列表
        List<ColumnDefinition> primaryKeyColumnDefinitions = lookupPrimaryKeyColumnDefinitions(modelType, DEFAULT_PRIMARY_KEY_FIELD_NAME);

        AssertUtil.assertTrue(primaryKeyColumnDefinitions != null && !primaryKeyColumnDefinitions.isEmpty(), "没有找到主键属性定义！");

        AssertUtil.assertTrue(primaryKeyColumnDefinitions.size() == primaryKeyValues.length,
                "找到主键[" + primaryKeyColumnDefinitions.size() + "], 但是给定的主键值有[" + primaryKeyValues.length + "]");

        GeneralSelectBuilder selectBuilder = new GeneralSelectBuilder(modelType);
        selectBuilder.customTableName(tableName);

        // 类型验证
        for (int i = 0; i < primaryKeyColumnDefinitions.size(); ++i) {
            Object value = primaryKeyValues[i];
            ColumnDefinition columnDefinition = primaryKeyColumnDefinitions.get(i);
            Class<?> fieldType = columnDefinition.getFieldType();
            AssertUtil.assertEquals(value.getClass(), columnDefinition.getFieldType(),
                    "主键类型和给定的值不匹配，第[" + (i + 1) + "]个主键类型为[" + fieldType.getName() + "], 传入的值类型为[" + value.getClass().getName() + "]");

            selectBuilder.addCondition(columnDefinition, value);

        }

        selectBuilder.limitOne();

        return selectBuilder;
    }

    /**
     * 根据Query对象查询
     *
     * @param tableName      指定数据库表名称
     * @param modelType      模型类型
     * @param queryCondition 查询条件
     * @return
     */
    public static SelectBuilder selectByQueryCondition(String tableName, Class<?> modelType, Object queryCondition) {
        AssertUtil.assertNotNull(modelType, "模型类型不能为空");

        return new SelectBuilder(modelType, queryCondition).customTableName(tableName);
    }


    /**
     * 根据Query对象查询
     *
     * @param modelType      模型类型
     * @param queryCondition 查询条件
     * @return
     */
    public static SelectBuilder selectByQueryCondition(Class<?> modelType, Object queryCondition) {
        return selectByQueryCondition(null, modelType, queryCondition);
    }

    /**
     * 更新模型对象， 更新逻辑如下：
     * 1. 查找 model 的主键是否有值，有的话就按照主键值进行更新
     * 2. 如果没有主键值，查找唯一键 @UniqueKey 字段进行更新
     * 3. 用户可自己定义WhereBuilder
     * <p>
     * 注： 默认会更新所有匹配的行，如果只想更新一行，请设置builder.setUpdateLimitCount(1)
     *
     * @param tableName 数据库表名称
     * @param model     要更新的模型对象
     * @param <T>       模型类型
     * @return
     */
    public static <T> UpdateBuilder updateModel(String tableName, T model) {
        AssertUtil.assertNotNull(model, "要更新的模型对象不能为空");
        return new UpdateBuilder(model, -1).customTableName(tableName);
    }

    /**
     * 更新模型对象， 更新逻辑如下：
     * 1. 用户可自己定义WhereBuilder
     * 2. 查找 model 的主键是否有值，有的话就按照主键值进行更新
     * 3. 如果没有主键值，查找唯一键 @UniqueKey 字段进行更新
     * <p>
     * 注： 默认会更新所有匹配的行，如果只想更新一行，请设置builder.setUpdateLimitCount(1)
     *
     * @param model 要更新的模型对象
     * @param <T>   模型类型
     * @return
     */
    public static <T> UpdateBuilder updateModel(T model) {
        return updateModel(null, model);
    }

    /**
     * 插入对象
     *
     * @param tableName 表名
     * @param model     模型
     * @param <T>
     * @return
     */
    public static <T> InsertBuilder insertModel(String tableName, T model) {
        return new InsertBuilder(model).customTableName(tableName);
    }

    /**
     * 插入对象
     *
     * @param model 模型
     * @param <T>
     * @return
     */
    public static <T> InsertBuilder insertModel(T model) {
        return insertModel(null, model);
    }

    /**
     * 根据主键删除指定对象， 默认只会删除一行记录，如果允许删除多行请调用 .setDeleteLimitCount(xxx)
     * 主键查找规则：
     * 1. 查找 具有 @PrimaryKey 的字段列表作为主键
     * 2. 第一步骤没有找到的话，就使用 id 作为主键ID
     *
     * @param modelType        模型类型
     * @param primaryKeyValues 主键值
     * @return
     */
    public static DeleteBuilder deleteByPrimaryKey(Class<?> modelType, Object... primaryKeyValues) {
        return deleteByPrimaryKey(null, modelType, primaryKeyValues);
    }

    /**
     * 根据主键删除指定对象， 默认只会删除一行记录，如果允许删除多行请调用 .setDeleteLimitCount(xxx)
     * 主键查找规则：
     * 1. 查找 具有 @PrimaryKey 的字段列表作为主键
     * 2. 第一步骤没有找到的话，就使用 id 作为主键ID
     *
     * @param tableName        表名
     * @param modelType        模型类型
     * @param primaryKeyValues 主键值
     * @return
     */
    public static DeleteBuilder deleteByPrimaryKey(String tableName, Class<?> modelType, Object... primaryKeyValues) {
        AssertUtil.assertNotNull(modelType, "模型类型不能为null");
        AssertUtil.assertTrue(primaryKeyValues != null && primaryKeyValues.length > 0, "必须提供主键值列表作为删除条件");

        int index = 1;
        for (Object primaryKeyValue : primaryKeyValues) {
            AssertUtil.assertNotNull(primaryKeyValue, "第[" + index++ + "]主键值不能为null");
        }

        // 获取主键列表
        List<ColumnDefinition> primaryKeyColumnDefinitions = lookupPrimaryKeyColumnDefinitions(modelType, DEFAULT_PRIMARY_KEY_FIELD_NAME);

        AssertUtil.assertTrue(primaryKeyColumnDefinitions != null && !primaryKeyColumnDefinitions.isEmpty(), "没有找到主键属性定义！");

        AssertUtil.assertTrue(primaryKeyColumnDefinitions.size() == primaryKeyValues.length,
                "找到主键[" + primaryKeyColumnDefinitions.size() + "], 但是给定的主键值有[" + primaryKeyValues.length + "]");


        WhereBuilder whereBuilder = new WhereBuilder(modelType);

        // 类型验证
        for (int i = 0; i < primaryKeyColumnDefinitions.size(); ++i) {
            Object value = primaryKeyValues[i];
            ColumnDefinition columnDefinition = primaryKeyColumnDefinitions.get(i);
            Class<?> fieldType = columnDefinition.getFieldType();
            AssertUtil.assertEquals(value.getClass(), columnDefinition.getFieldType(),
                    "主键类型和给定的值不匹配，第[" + (i + 1) + "]个主键类型为[" + fieldType.getName() + "], 传入的值类型为[" + value.getClass().getName() + "]");

            whereBuilder.andEqualByModelFieldName(columnDefinition.getFieldName(), value);

        }

        return new DeleteBuilder(modelType, 1, whereBuilder).customTableName(tableName);
    }

    /**
     * 查找指定模型类型的主键字段定义，会先扫描 @PrimaryKey 注解， 没有的话就返回给定主键属性名称
     *
     * @param modelType                   模型定义
     * @param defaultPrimaryKeyFieldNames 默认的主键属性名称列表，如果在 @PrimaryKey 没有扫描到的话就会按照这个进行查找主键属性
     * @return 如果有的话就返回，没有就返回一个空的list
     */
    public static List<ColumnDefinition> lookupPrimaryKeyColumnDefinitions(Class<?> modelType, String... defaultPrimaryKeyFieldNames) {

        List<ColumnDefinition> list = ModelDefinitionContext.getPrimaryKeyColumnDefinitions(modelType);

        if (Util.isEmpty(list)) {
            list = lookupColumnDefinitionsByFieldNames(modelType, defaultPrimaryKeyFieldNames);
        }

        return list;
    }

    /**
     * 根据模型属性名称查找模型列定义
     *
     * @param modelType       模型属性类型
     * @param modelFieldNames 模型属性名称
     * @return 如果有的话就返回，没有就返回一个空的list
     * @throws net.oschina.arvin.sqlbuilder.exceptions.SqlBuilderException 如果有不存在的模型属性名称就会抛出异常
     */
    public static List<ColumnDefinition> lookupColumnDefinitionsByFieldNames(Class<?> modelType, String... modelFieldNames) {
        return ModelDefinitionContext.getColumnDefinitionsByModelFieldNames(modelType, modelFieldNames);
    }
}
